﻿namespace Community.Ssis.RankTransformUi
{
    using Microsoft.SqlServer.Dts.Pipeline;
    using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
    using Microsoft.SqlServer.Dts.Runtime.Wrapper;
    using System;
    using System.Runtime.InteropServices;

    [DtsPipelineComponent(
        DisplayName = "Rank Transform",
        Description = "Apply RANK and DENSE_RANK functions to a sorted data flow.",
        UITypeName = "Community.Ssis.RankTransformUi.RankTransformUi,Community.Ssis.RankTransformUi,Version=1.0.0.0,Culture=neutral,PublicKeyToken=c934fa7f7d2738c2",
        ComponentType = ComponentType.Transform)]
    public class RankTransform : PipelineComponent
    {
        private ColumnInfo[] _columnInfos;
        private int _denseRank;
        private int _outputColumnDenseRankLineageId = -1;
        private int _outputColumnRankLineageId = -1;
        private int _outputColumnRowNumberLineageId = -1;
        private int _outputColumnRowNumberByPartitionLineageId = -1;
        private object[] _partitionValues;
        private int _rank;
        private int _rankCounter;
        private int _rowNumber;
        private int _rowNumberByPartition;
        public const string IsPartitionColumnProperty = "IsPartitionColumn";
        public const string RankTransformFunctionProperty = "RankTransformFunction";

        public override void DeleteInput(int inputId)
        {
            PostError("Component requires exactly one input. Deleted input is forbidden.");
            throw new PipelineComponentHResultException(-1071611888);
        }

        public override void DeleteOutput(int outputId)
        {
            PostError("Component requires exactly two outputs. Deleted output is forbidden.");
            throw new PipelineComponentHResultException(-1071611887);
        }

        public override IDTSInput100 InsertInput(DTSInsertPlacement insertPlacement, int inputId)
        {
            PostError("Component requires exactly one input. New input is forbidden.");
            throw new PipelineComponentHResultException(-1071611890);
        }

        public override IDTSOutput100 InsertOutput(DTSInsertPlacement insertPlacement, int outputId)
        {
            PostError("Component requires exactly two outputs. New output is forbidden.");
            throw new PipelineComponentHResultException(-1071611889);
        }

        public override IDTSOutputColumn100 InsertOutputColumnAt(int outputId, int outputColumnIndex, string name, string description)
        {
            var column = base.InsertOutputColumnAt(outputId, outputColumnIndex, name, description);
            column.SetDataTypeProperties(DataType.DT_I4, 0, 0, 0, 0);
            var property = column.CustomPropertyCollection.New();
            property.Name = "RankTransformFunction";
            property.Description = "Describes the Rank Transform Function for the Output Column.";
            property.TypeConverter = typeof(RankTransformFunction).AssemblyQualifiedName;
            return column;
        }

        private void PostError(string message)
        {
            bool flag;
            ComponentMetaData.FireError(0, ComponentMetaData.Name, message, "", 0, out flag);
        }

        public override void PreExecute()
        {
            int num3;
            _outputColumnRankLineageId = -1;
            _outputColumnDenseRankLineageId = -1;
            _outputColumnRowNumberLineageId = -1;
            _rank = 0;
            _denseRank = 0;
            _rowNumber = 0;
            _rowNumberByPartition = 0;
            var input = ComponentMetaData.InputCollection[0];
            var cos = input.InputColumnCollection;
            var num = input.Buffer;
            var num2 = cos.Count;
            _columnInfos = new ColumnInfo[num2];
            _partitionValues = new object[num2];
            for (num3 = 0; num3 < num2; num3++)
            {
                var column = cos[num3];
                var info = new ColumnInfo
                               {
                                   BufferColumnIndex = BufferManager.FindColumnByLineageID(num, column.LineageID),
                                   LineageId = column.LineageID,
                                   IsPartition = (bool)column.CustomPropertyCollection[0].Value
                               };
                _columnInfos[column.SortKeyPosition - 1] = info;
            }
            var cos2 = ComponentMetaData.OutputCollection[0].OutputColumnCollection;
            var num4 = cos2.Count;
            for (num3 = 0; num3 < num4; num3++)
            {
                var column2 = cos2[num3];
                switch (((RankTransformFunction)column2.CustomPropertyCollection[0].Value))
                {
                    case RankTransformFunction.Rank:
                        _outputColumnRankLineageId = BufferManager.FindColumnByLineageID
                                                            (num, column2.LineageID);
                        break;

                    case RankTransformFunction.DenseRank:
                        _outputColumnDenseRankLineageId = BufferManager.FindColumnByLineageID
                                                            (num, column2.LineageID);
                        break;

                    case RankTransformFunction.RowNumber:
                        _outputColumnRowNumberLineageId = BufferManager.FindColumnByLineageID(num, column2.LineageID);
                        break;

                    case RankTransformFunction.RowNumberByPartition:
                        _outputColumnRowNumberByPartitionLineageId = BufferManager.FindColumnByLineageID(num, column2.LineageID);
                        break;
                }
            }
        }

        public override void ProcessInput(int inputId, PipelineBuffer buffer)
        {
            var columnsInfosLength = _columnInfos.Length;
            while (buffer.NextRow())
            {
                var flag = true;
                var currentColumnIsPartition = true;
                for (var i = 0; i < columnsInfosLength; i++)
                {
                    var info = _columnInfos[i];
                    var obj2 = buffer[info.BufferColumnIndex];
                    var obj3 = _partitionValues[i];
                    if (flag && (((obj2 != null) || (obj3 != null)) && ((obj2 == null) || !obj2.Equals(obj3))))
                    {
                        if (info.IsPartition)
                        {
                            currentColumnIsPartition = false;
                        }
                        flag = false;
                    }
                    _partitionValues[i] = obj2;
                }
                _rowNumber++;
                _rowNumberByPartition++;
                if (flag)
                {
                    _rankCounter++;
                }
                else if (currentColumnIsPartition)
                {
                    if (_rank < _rankCounter)
                    {
                        _rank = _rankCounter;
                    }
                    _rank++;
                    _denseRank++;
                    _rankCounter = _rank;
                }
                else
                {
                    _rank = 1;
                    _denseRank = 1;
                    _rankCounter = 1;
                    _rowNumberByPartition = 1;
                }
                if (_outputColumnDenseRankLineageId >= 0)
                {
                    buffer.SetInt32(_outputColumnDenseRankLineageId, _denseRank);
                }
                if (_outputColumnRankLineageId >= 0)
                {
                    buffer.SetInt32(_outputColumnRankLineageId, _rank);
                }
                if (_outputColumnRowNumberLineageId >= 0)
                {
                    buffer.SetInt32(_outputColumnRowNumberLineageId, _rowNumber);
                }
                if (_outputColumnRowNumberByPartitionLineageId >= 0)
                {
                    buffer.SetInt32(_outputColumnRowNumberByPartitionLineageId, _rowNumberByPartition);
                }
            }
        }

        public override void ProvideComponentProperties()
        {
            base.ProvideComponentProperties();
            ComponentMetaData.ContactInfo = "Rank Transform; Jamie Thomson; Source code is available on Codeplex, feel free to modify.";
            var output = ComponentMetaData.OutputCollection[0];
            output.Name = "Rank Output";
            output.SynchronousInputID = ComponentMetaData.InputCollection[0].ID;
        }

        public override void ReinitializeMetaData()
        {
            if (!ComponentMetaData.AreInputColumnsValid)
            {
                ComponentMetaData.RemoveInvalidInputColumns();
            }
            base.ReinitializeMetaData();
        }

        private static int SequentialSum(int count)
        {
            return (int)Math.Round((1 + count) * (count / 2.0), 0);
        }

        public override IDTSCustomProperty100 SetOutputColumnProperty(int outputId, int outputColumnId,
            string propertyName, object propertyValue)
        {
            if (propertyName == "RankTransformFunction")
            {
                if (propertyValue == null)
                {
                    PostError(string.Format("Invalid value for '{0}' output column property. Value cannot be null.", "RankTransformFunction"));
                    throw new PipelineComponentHResultException(-1071628278);
                }
                try
                {
                    var function = (RankTransformFunction)Enum.Parse(typeof(RankTransformFunction), propertyValue.ToString(), true);
                }
                catch (ArgumentException exception)
                {
                    PostError(string.Format("Invalid value for '{0}' output column property. {1}", "RankTransformFunction", exception.Message));
                    throw new PipelineComponentHResultException(-1071628278);
                }
                return base.SetOutputColumnProperty(outputId, outputColumnId, propertyName, propertyValue);
            }
            if (propertyName == "Name")
            {
                return base.SetOutputColumnProperty(outputId, outputColumnId, propertyName, propertyValue);
            }
            PostError(string.Format("Invalid property name '{0}'.", propertyName));
            throw new PipelineComponentHResultException(-1071628278);
        }

        public override IDTSInputColumn100 SetUsageType(int inputId, IDTSVirtualInput100 virtualInput, int lineageId, DTSUsageType usageType)
        {
            IDTSVirtualInputColumn100 virtualInputColumnByLineageId;
            IDTSInputColumnCollection100 cos;
            if (usageType == 0)
            {
                virtualInputColumnByLineageId = virtualInput.VirtualInputColumnCollection.GetVirtualInputColumnByLineageID(lineageId);
                if (virtualInputColumnByLineageId.SortKeyPosition == 0)
                {
                    PostError(string.Format("Selected input columns must be part of the sort key. Column {0} is not part of the sort key.", virtualInputColumnByLineageId.Name));
                    throw new PipelineComponentHResultException(-1071628253);
                }
                var num = virtualInputColumnByLineageId.SortKeyPosition;
                cos = ComponentMetaData.InputCollection[0].InputColumnCollection;
                foreach (IDTSInputColumn100 column3 in cos)
                {
                    num += column3.SortKeyPosition;
                }
                if (SequentialSum(cos.Count + 1) != num)
                {
                    PostError(string.Format("Input column selection must follow column sort order. Sequential sum check violation for {0} with SortKeyPosition of {1}.", virtualInputColumnByLineageId.Name, virtualInputColumnByLineageId.SortKeyPosition));
                    throw new PipelineComponentHResultException(-1071628253);
                }
                var column = base.SetUsageType(inputId, virtualInput, lineageId, usageType);
                if (column.CustomPropertyCollection.Count > 0)
                {
                    column.CustomPropertyCollection.RemoveAll();
                }
                var property = column.CustomPropertyCollection.New();
                property.Name = "IsPartitionColumn";
                property.Description = "Determines whether selected column is partition or order by column within ranking function.";
                property.TypeConverter = typeof(bool).AssemblyQualifiedName;
                property.Value = false;
                return column;
            }
            if (usageType == (DTSUsageType)1)
            {
                PostError("Component requires that input columns be marked read-only.");
                throw new PipelineComponentHResultException(-1071616000);
            }
            virtualInputColumnByLineageId = virtualInput.VirtualInputColumnCollection.GetVirtualInputColumnByLineageID(lineageId);
            cos = ComponentMetaData.InputCollection[0].InputColumnCollection;
            if (cos.Count != virtualInputColumnByLineageId.SortKeyPosition)
            {
                PostError(string.Format("Input column de-selection must follow column reverse sort order. Column {0} with sort index of {1} is not the last selected column, see sort index column {2}.", virtualInputColumnByLineageId.Name, virtualInputColumnByLineageId.SortKeyPosition, cos.Count));
                throw new PipelineComponentHResultException(-1071628253);
            }
            return base.SetUsageType(inputId, virtualInput, lineageId, usageType);
        }

        public override DTSValidationStatus Validate()
        {
            if (ComponentMetaData.InputCollection.Count != 1)
            {
                PostError("Component requires exactly one input.");
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            if (ComponentMetaData.OutputCollection.Count != 1)
            {
                PostError("Component requires exactly one output.");
                return DTSValidationStatus.VS_ISCORRUPT;
            }
            if (!ComponentMetaData.AreInputColumnsValid)
            {
                return DTSValidationStatus.VS_NEEDSNEWMETADATA;
            }
            var cos = ComponentMetaData.InputCollection[0].InputColumnCollection;
            var num = 0;
            foreach (IDTSInputColumn100 column in cos)
            {
                if (column.SortKeyPosition > 0)
                {
                    num += column.SortKeyPosition;
                }
                else
                {
                    PostError(string.Format("Column {0} is not part of the sort key. Columns selected must be seuqentially ordered within the sort key.", column.Name));
                    return DTSValidationStatus.VS_ISBROKEN;
                }
            }
            if (SequentialSum(cos.Count) != num)
            {
                PostError(string.Format("Input columns are not sequentially sorted. Sequential sum check violation.", new object[0]));
                return DTSValidationStatus.VS_ISBROKEN;
            }
            var cos2 = ComponentMetaData.OutputCollection[0].OutputColumnCollection;
            foreach (IDTSOutputColumn100 column2 in cos2)
            {
                var num2 = column2.CustomPropertyCollection.Count;
                if (num2 == 1)
                {
                    if (column2.CustomPropertyCollection[0].Name != "RankTransformFunction")
                    {
                        PostError(string.Format("Output column {0} custom property '{1}' is invalid. Output columns require the {2} custom property only."
                                                        , column2.Name
                                                        , column2.CustomPropertyCollection[0].Name
                                                        , "RankTransformFunction"));
                        return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                    }
                    if (column2.CustomPropertyCollection[0].Value == null)
                    {
                        PostError(string.Format("Invalid value for {0} output column property {1}. Value cannot be null."
                                                        , column2.Name
                                                        , "RankTransformFunction"));
                        return DTSValidationStatus.VS_ISBROKEN;
                    }
                    if ((column2.CustomPropertyCollection[0].Value == null) || (column2.CustomPropertyCollection[0].Value.ToString().Length == 0))
                    {
                        PostError(string.Format("Invalid value for {0} output column property {1}. Value cannot be null or empty.", column2.Name, "RankTransformFunction"));
                        return DTSValidationStatus.VS_ISBROKEN;
                    }
                    try
                    {
                        var function = (RankTransformFunction)Enum.Parse(typeof(RankTransformFunction), column2.CustomPropertyCollection[0].Value.ToString(), true);
                    }
                    catch (ArgumentException exception)
                    {
                        PostError(string.Format("Invalid value for {0} output column property {1}. {2}", column2.Name, "RankTransformFunction", exception.Message));
                        return DTSValidationStatus.VS_ISBROKEN;
                    }
                }
                else
                {
                    PostError(string.Format("Output column {0} has {1} custom properties. Output columns require the {2} custom property only.", column2.Name, num2, "RankTransformFunction"));
                    return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                }
            }
            return base.Validate();
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct ColumnInfo
        {
            public int BufferColumnIndex;
            public int LineageId;
            public bool IsPartition;
        }
    }
}

